import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'dart:ui' as ui;

import 'package:archive/archive.dart';
import 'package:butterfly/api/file_system.dart';
import 'package:butterfly/cubits/transform.dart';
import 'package:butterfly/helpers/asset.dart';
import 'package:butterfly/services/asset.dart';
import 'package:butterfly_api/butterfly_text.dart' as text;
import 'package:flutter/foundation.dart';
import 'package:butterfly/bloc/document_bloc.dart';
import 'package:butterfly/dialogs/import/confirmation.dart';
import 'package:butterfly/dialogs/import/note.dart';
import 'package:butterfly/dialogs/load.dart';
import 'package:butterfly/models/defaults.dart';
import 'package:butterfly/renderers/renderer.dart';
import 'package:butterfly_api/butterfly_api.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:lw_file_system/lw_file_system.dart';
import 'package:lw_sysapi/lw_sysapi.dart';
import 'package:material_leap/material_leap.dart';
import 'package:butterfly/src/generated/i18n/app_localizations.dart';
import 'package:pdfrx/pdfrx.dart';
import 'package:super_clipboard/super_clipboard.dart';
import 'package:meta/meta.dart';

import '../api/save.dart';
import '../cubits/current_index.dart';
import '../cubits/settings.dart';
import '../dialogs/export/general.dart';
import '../dialogs/import/pages.dart';
import '../dialogs/export/pdf.dart';

class ImportResult {
  final ImportService service;
  final NoteData? document;
  final List<PadElement> elements;
  final Map<String, Uint8List> assets;
  final List<(String?, DocumentPage)> pages;
  final List<Area> areas;
  final List<NoteData> packs;
  final bool choosePosition;
  final bool documentReady;

  ImportResult({
    required this.service,
    required this.document,
    this.elements = const [],
    this.assets = const {},
    this.pages = const [],
    this.areas = const [],
    this.packs = const [],
    this.choosePosition = false,
  }) : documentReady = false;
  ImportResult.ready({required this.service, required this.document})
    : elements = const [],
      assets = const {},
      pages = const [],
      areas = const [],
      packs = const [],
      choosePosition = false,
      documentReady = true;

  Future<NoteData> export() async {
    final currentDocument = this.document;
    if (documentReady && currentDocument != null) {
      return currentDocument;
    }
    var document =
        currentDocument ??
        DocumentDefaults.createDocument(createDefaultPage: false);
    final state = service._getState();
    DocumentPage page =
        state?.page ?? document.getPage() ?? DocumentDefaults.createPage();
    List<PadElement> imported;
    (document, imported) = await importAssetsAsync(
      document,
      elements,
      assets: assets,
    );
    if (imported.isNotEmpty) {
      page = page.copyWith(
        layers: [
          DocumentLayer(
            content: [...page.content, ...imported],
            id: createUniqueId(),
          ),
        ],
      );
      document = document.setPage(page).$1;
    }
    for (final (name, page) in pages) {
      (document, _) = document.addPage(page, name ?? '');
    }
    for (final pack in packs) {
      document = document.setBundledPack(pack);
    }
    return document;
  }

  void submit({bool? choosePosition}) {
    choosePosition ??= this.choosePosition;
    final state = service._getState();
    final context = service.context;
    final bloc = service.bloc;
    if (choosePosition &&
        state != null &&
        (elements.isNotEmpty || areas.isNotEmpty)) {
      state.currentIndexCubit.changeTemporaryHandler(
        context,
        ImportTool(elements: elements, areas: areas, assets: assets),
        bloc: bloc!,
        temporaryState: TemporaryState.removeAfterRelease,
      );
    } else {
      bloc
        ?..add(AreasCreated(areas))
        ..add(ElementsCreated(elements));
    }
    bloc?.add(
      PagesAdded(
        pages
            .map((e) => PageAddedDetails(page: e.$2, name: e.$1 ?? ''))
            .toList(),
      ),
    );
    for (final pack in packs) {
      bloc?.add(PackAdded(pack));
    }
  }
}

class ImportService {
  final DocumentBloc? bloc;
  final BuildContext context;
  final ExternalStorage? storage;
  final bool useDefaultStorage;

  ImportService(
    this.context, {
    this.bloc,
    this.storage,
    this.useDefaultStorage = true,
  });

  DocumentLoadSuccess? _getState() => bloc?.state is DocumentLoadSuccess
      ? (bloc?.state as DocumentLoadSuccess)
      : null;
  CurrentIndexCubit? get currentIndexCubit => _getState()?.currentIndexCubit;
  SettingsCubit getSettingsCubit() => context.read<SettingsCubit>();
  ButterflySettings getSettings() => getSettingsCubit().state;
  ButterflyFileSystem getFileSystem() => context.read<ButterflyFileSystem>();
  DocumentFileSystem getDocumentSystem() => useDefaultStorage
      ? getFileSystem().buildDefaultDocumentSystem()
      : getFileSystem().buildDocumentSystem(storage);
  TemplateFileSystem getTemplateFileSystem() => useDefaultStorage
      ? getFileSystem().buildDefaultTemplateSystem()
      : getFileSystem().buildTemplateSystem(storage);
  PackFileSystem getPackFileSystem() => useDefaultStorage
      ? getFileSystem().buildDefaultPackSystem()
      : getFileSystem().buildPackSystem(storage);
  Future<NamedItem<text.TextStyleSheet>?> findStyleSheet() async {
    final fileSystem = getPackFileSystem();
    for (final pack in await fileSystem.getFiles()) {
      final styleSheet = pack.data!.getNamedStyles().firstOrNull;
      if (styleSheet != null) {
        return styleSheet;
      }
    }
    return null;
  }

  @useResult
  Future<ImportResult?> load({
    String type = '',
    Object? data,
    NoteData? document,
  }) async {
    final location = bloc?.state.currentIndexCubit?.state.location;
    Uint8List? bytes;
    final fs = getDocumentSystem();
    if (data is Uint8List) {
      bytes = data;
    } else if (data is String) {
      bytes = Uint8List.fromList(utf8.encode(data));
    } else if (data is FileSystemFile<NoteData>) {
      bytes = Uint8List.fromList(data.data?.exportAsBytes() ?? []);
    } else if (data is FileSystemFile<NoteFile>) {
      bytes = Uint8List.fromList(data.data?.data ?? []);
    } else if (location != null) {
      bytes = await fs.loadAbsolute(location.path);
    } else if (data is List) {
      bytes = Uint8List.fromList(List<int>.from(data));
    } else if (data is NoteData) {
      return _importDocument(data, document: document, advanced: false);
    } else if (data is NoteFile) {
      bytes = data.data;
    }
    if (type.isEmpty) type = 'note';
    final fileType = AssetFileType.values.firstWhereOrNull(
      (element) =>
          element.isMimeType(type) ||
          element.getFileExtensions().contains(type) ||
          element.name == type,
    );
    if (fileType == null) {
      await showDialog(
        context: context,
        builder: (context) => UnknownImportConfirmationDialog(
          message: AppLocalizations.of(context).unknownImportType,
        ),
      );
      return null;
    }
    if (bytes == null) return null;
    return import(fileType, bytes, document: document, advanced: false);
  }

  @useResult
  Future<ImportResult?> import(
    AssetFileType type,
    Uint8List bytes, {
    NoteData? document,
    Offset? position,
    bool advanced = true,
    DocumentFileSystem? fileSystem,
    TemplateFileSystem? templateSystem,
    PackFileSystem? packSystem,
  }) async {
    final realDocument =
        document ?? bloc?.state.data ?? DocumentDefaults.createDocument();
    return switch (type) {
      AssetFileType.note || AssetFileType.textNote => importBfly(
        bytes,
        document: document,
        position: position,
        advanced: advanced,
        templateSystem: templateSystem,
        packSystem: packSystem,
      ),
      AssetFileType.image => importImage(
        bytes,
        realDocument,
        position: position,
      ),
      AssetFileType.svg => importSvg(bytes, realDocument, position: position),
      AssetFileType.markdown => importText(
        bytes,
        realDocument,
        position: position,
        isMarkdown: true,
      ),
      AssetFileType.pdf => importPdf(
        bytes,
        realDocument,
        position: position,
        advanced: advanced,
      ),
      AssetFileType.page => importPage(bytes, realDocument, position: position),
      AssetFileType.xopp => importXopp(bytes, realDocument, position: position),
      AssetFileType.archive => importArchive(
        bytes,
        fileSystem: fileSystem,
      ).then((value) => null),
      AssetFileType.rawText => importText(
        bytes,
        realDocument,
        position: position,
        isMarkdown: false,
      ),
    };
  }

  Future<Uint8List?>? _readFileFromClipboard(
    DataReader reader,
    FileFormat format,
  ) {
    final c = Completer<Uint8List?>();
    final progress = reader.getFile(
      format,
      (file) async {
        try {
          final all = await file.readAll();
          c.complete(all);
        } catch (e) {
          c.completeError(e);
        }
      },
      onError: (e) {
        c.completeError(e);
      },
    );
    if (progress == null) {
      c.complete(null);
    }
    return c.future;
  }

  @useResult
  Future<ImportResult?> importClipboard(
    NoteData document, {
    Offset? position,
    bool advanced = true,
  }) async {
    Uint8List? data;
    AssetFileType? type;
    final clipboard = SystemClipboard.instance;
    if (clipboard != null) {
      final reader = await clipboard.read();
      final result = AssetFileType.values
          .map((e) {
            final format = e.getClipboardFormats().firstWhereOrNull(
              (f) => reader.canProvide(f),
            );
            return format == null ? null : (e, format);
          })
          .nonNulls
          .firstOrNull;
      if (result == null) return null;
      if (result.$2 is FileFormat) {
        data = await _readFileFromClipboard(reader, result.$2 as FileFormat);
      } else if (result.$2 is ValueFormat<Uint8List>) {
        data = await reader.readValue(result.$2 as ValueFormat<Uint8List>);
      }
      type = result.$1;
    } else {
      final clipboard = context.read<ClipboardManager>();
      final content = clipboard.getContent();
      data = content?.data;
      try {
        type = AssetFileType.values.byName(content?.type ?? '');
      } catch (e) {
        await showDialog(
          context: context,
          builder: (context) =>
              UnknownImportConfirmationDialog(message: e.toString()),
        );
      }
    }
    if (data == null || type == null) return null;
    return import(
      type,
      data,
      document: document,
      position: position,
      advanced: advanced,
    );
  }

  @useResult
  Future<ImportResult?> importBfly(
    Uint8List bytes, {
    NoteData? document,
    Offset? position,
    bool advanced = true,
    TemplateFileSystem? templateSystem,
    PackFileSystem? packSystem,
  }) async {
    try {
      final file = NoteFile(bytes);
      String? password;
      if (file.isEncrypted()) {
        password = await showDialog<String>(
          context: context,
          builder: (context) => NameDialog(
            title: AppLocalizations.of(context).encrypted,
            hint: AppLocalizations.of(context).password,
            button: AppLocalizations.of(context).open,
            obscureText: true,
          ),
        );
        if (password == null) return null;
      }
      final data = file.load(password: password);
      if (data == null) {
        return showDialog(
          context: context,
          builder: (context) => UnknownImportConfirmationDialog(
            message: AppLocalizations.of(context).unknownImportType,
          ),
        ).then((value) => null);
      }
      if (!data.isValid) {
        final archive = ZipDecoder().decodeBytes(bytes);
        await _importArchive(archive);
        return null;
      }
      final type = data.getMetadata()?.type;
      return switch (type) {
        NoteFileType.document => _importDocument(
          data,
          document: document,
          advanced: advanced,
        ),
        NoteFileType.template => _importTemplate(data, templateSystem),
        NoteFileType.pack => _importPack(
          data,
          document,
          packSystem,
        ).then((value) => null),
        _ => showDialog(
          context: context,
          builder: (context) => UnknownImportConfirmationDialog(
            message: AppLocalizations.of(context).unknownImportType,
          ),
        ).then((value) => null),
      };
    } catch (e) {
      await showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    }
    return null;
  }

  Future<ImportResult?> _importDocument(
    NoteData data, {
    NoteData? document,
    bool advanced = true,
  }) async {
    var pages = data.getPages();
    var packs = data.getBundledPacks().toList();
    if (advanced) {
      final callback = await showDialog<NoteDialogCallback>(
        context: context,
        builder: (context) => NoteImportDialog(pages: pages, packs: packs),
      );
      if (callback == null) return null;
      pages = callback.pages;
      packs = callback.packs;
    } else if (document == null) {
      return ImportResult.ready(service: this, document: data);
    }
    return ImportResult(
      service: this,
      document: document,
      pages: pages
          .map((e) {
            final page = data.getPage(e);
            if (page == null) return null;
            return (e, page);
          })
          .nonNulls
          .toList(),
      packs: packs.map((e) => data.getBundledPack(e)).nonNulls.toList(),
    );
  }

  @useResult
  Future<ImportResult?> importPage(
    Uint8List bytes,
    NoteData document, {
    Offset? position,
  }) async {
    try {
      final page = DocumentPage.fromJson(json.decode(utf8.decode(bytes)));
      return _importPage(page, document, position);
    } catch (e) {
      await showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    }
    return null;
  }

  Future<ImportResult?> _importPage(
    DocumentPage? page,
    NoteData document, [
    Offset? position,
  ]) async {
    final firstPos = position ?? Offset.zero;
    if (page == null) return null;
    final areas = page.areas
        .map((e) => e.copyWith(position: e.position + firstPos.toPoint()))
        .toList();

    final renderers = page.content
        .map((e) => e.copyWith(id: createUniqueId()))
        .map((e) => Renderer.fromInstance(e))
        .toList();
    final transformCubit = TransformCubit(1, position);
    final assetService = AssetService();
    await Future.wait(
      renderers.map(
        (e) async =>
            await e.setup(transformCubit, document, assetService, page),
      ),
    );
    assetService.dispose();
    final content = renderers
        .map(
          (e) =>
              e.transform(position: firstPos, relative: true)?.element ??
              e.element,
        )
        .toList();
    return ImportResult(
      service: this,
      document: document,
      elements: content,
      areas: areas,
      choosePosition: position == null,
    );
  }

  Future<ImportResult?> _importTemplate(
    NoteData template, [
    TemplateFileSystem? templateSystem,
  ]) async {
    final metadata = template.getMetadata();
    templateSystem ??= getTemplateFileSystem();
    if (metadata == null) return null;
    final result = await showDialog<bool>(
      context: context,
      builder: (context) =>
          TemplateImportConfirmationDialog(template: metadata),
    );
    if (context.mounted && result == true) {
      templateSystem.createFile(template.name ?? '', template);
    }
    return ImportResult(service: this, document: template.createDocument());
  }

  Future<bool> _importPack(
    NoteData pack, [
    NoteData? document,
    PackFileSystem? packSystem,
  ]) async {
    packSystem ??= getPackFileSystem();
    final metadata = pack.getMetadata();
    if (metadata == null) return false;
    final result = await showDialog<bool>(
      context: context,
      builder: (context) => PackImportConfirmationDialog(pack: metadata),
    );
    if (result != true) return false;
    if (context.mounted) {
      if (document != null) {
        document = document.setBundledPack(pack);
      } else {
        packSystem.createFile(pack.name ?? '', pack);
      }
    }
    return true;
  }

  @useResult
  Future<ImportResult?> importImage(
    Uint8List bytes,
    NoteData document, {
    Offset? position,
  }) async {
    try {
      final screen = MediaQuery.of(context).size;
      final firstPos = position ?? Offset.zero;
      final codec = await ui.instantiateImageCodec(bytes);
      final frame = await codec.getNextFrame();
      final image = frame.image;

      final newBytes = await image.toByteData(format: ui.ImageByteFormat.png);
      final state = _getState();
      if (newBytes == null) return null;
      final newData = newBytes.buffer.asUint8List();
      final dataPath = Uri.dataFromBytes(
        newData,
        mimeType: 'image/png',
      ).toString();
      final height = image.height.toDouble(), width = image.width.toDouble();
      image.dispose();
      final settingsScale = getSettingsCubit().state.imageScale;
      ElementConstraints? constraints;
      if (position == null && currentIndexCubit != null && settingsScale > 0) {
        final scale =
            min(
              (screen.width * settingsScale) / width,
              (screen.height * settingsScale) / height,
            ) /
            currentIndexCubit!.state.cameraViewport.scale;
        constraints = ElementConstraints.scaled(scaleX: scale, scaleY: scale);
      }
      return ImportResult(
        service: this,
        document: document,
        elements: [
          ImageElement(
            height: height,
            width: width,
            collection: state?.currentCollection ?? '',
            source: dataPath,
            constraints: constraints,
            position: firstPos.toPoint(),
          ),
        ],
        choosePosition: position == null,
      );
    } catch (e) {
      showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    }
    return null;
  }

  @useResult
  Future<ImportResult?> importXopp(
    Uint8List bytes,
    NoteData document, {
    Offset? position,
  }) async {
    try {
      final data = xoppMigrator(bytes);
      return _importDocument(data, document: document);
    } catch (e) {
      showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    }
    return null;
  }

  @useResult
  Future<ImportResult?> importSvg(
    Uint8List bytes,
    NoteData document, {
    Offset? position,
  }) async {
    try {
      final screen = MediaQuery.of(context).size;
      final firstPos = position ?? Offset.zero;
      final contentString = String.fromCharCodes(bytes);
      try {
        var info = await vg.loadPicture(SvgStringLoader(contentString), null);
        final size = info.size;
        var height = size.height, width = size.width;
        if (!height.isFinite) height = 0;
        if (!width.isFinite) width = 0;
        final state = _getState();
        String dataPath;
        if (state != null) {
          dataPath = Uri.dataFromBytes(
            bytes,
            mimeType: 'image/svg+xml',
          ).toString();
        } else {
          dataPath = UriData.fromBytes(
            bytes,
            mimeType: 'image/svg+xml',
          ).toString();
        }
        final settingsScale = getSettingsCubit().state.imageScale;
        ElementConstraints? constraints;
        if (position == null &&
            currentIndexCubit != null &&
            settingsScale > 0) {
          final scale =
              min(
                (screen.width * settingsScale) / width,
                (screen.height * settingsScale) / height,
              ) /
              currentIndexCubit!.state.cameraViewport.scale;
          constraints = ElementConstraints.scaled(scaleX: scale, scaleY: scale);
        }
        return ImportResult(
          service: this,
          document: document,
          elements: [
            SvgElement(
              width: width,
              height: height,
              source: dataPath,
              constraints: constraints,
              position: firstPos.toPoint(),
            ),
          ],
          choosePosition: position == null,
        );
      } catch (e) {
        showDialog<void>(
          context: context,
          builder: (context) =>
              UnknownImportConfirmationDialog(message: e.toString()),
        );
      }
    } catch (e) {
      showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    }
    return null;
  }

  @useResult
  Future<ImportResult?> importText(
    Uint8List bytes,
    NoteData document, {
    Offset? position,
    required bool isMarkdown,
  }) async {
    try {
      final firstPos = position ?? Offset.zero;
      final contentString = String.fromCharCodes(bytes);
      final state = _getState();
      final background =
          state?.page.backgrounds.firstOrNull?.defaultColor ?? SRGBColor.white;
      final foreground = background.toColor().isDark()
          ? SRGBColor.white
          : SRGBColor.black;
      final styleSheet = await findStyleSheet();
      return ImportResult(
        service: this,
        document: document,
        elements: [
          isMarkdown
              ? MarkdownElement(
                  position: firstPos.toPoint(),
                  text: contentString,
                  foreground: foreground,
                  styleSheet: styleSheet,
                )
              : TextElement(
                  area: text.TextArea(
                    paragraph: text.TextParagraph(
                      textSpans: [text.TextSpan(text: contentString)],
                    ),
                  ),
                  styleSheet: styleSheet,
                  foreground: foreground,
                ),
        ],
        choosePosition: position == null,
      );
    } catch (e) {
      showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    }
    return null;
  }

  static const _pdfImportSource = '$kAssetScheme://imported_pdf';

  @useResult
  Future<ImportResult?> importPdf(
    Uint8List bytes,
    NoteData document, {
    Offset? position,
    bool advanced = true,
  }) async {
    LoadingDialogHandler? dialog;
    PdfDocument? pdfDocument;
    try {
      final firstPos = position ?? Offset.zero;
      final localizations = AppLocalizations.of(context);
      pdfDocument = await PdfDocument.openData(bytes);
      final elements = pdfDocument.pages;
      if (!context.mounted) return null;
      List<int> pages = List.generate(elements.length, (index) => index);
      bool spreadToPages = false, createAreas = false, invert = false;
      SRGBColor background = BasicColors.whiteTransparent;
      String name = '';
      if (advanced) {
        List<ui.Image> images = [];
        final dialog = showLoadingDialog(context);

        for (int i = 0; i < elements.length; i++) {
          final raster = elements[i];
          dialog?.setProgress(i / elements.length);
          final pdfImage = await raster.render();
          if (pdfImage == null) continue;
          images.add(await pdfImage.createImage());
        }
        dialog?.close();
        final callback = await showDialog<PageDialogCallback>(
          context: context,
          builder: (context) => PagesDialog(pages: images),
        );
        for (var image in images) {
          try {
            image.dispose();
          } catch (_) {}
        }
        if (callback == null) return null;
        pages = callback.pages;
        spreadToPages = callback.spreadToPages;
        createAreas = callback.createAreas;
        invert = callback.invert;
        background = callback.background;
        name = callback.name;
      }
      String getPageName(int index) {
        if (name.isEmpty) return localizations.pageIndex(index + 1);
        return '$name $index';
      }

      dialog = showLoadingDialog(context);
      final selectedElements = <PdfElement>[];
      final areas = <Area>[];
      final documentPages = <(String?, DocumentPage)>[];
      var y = firstPos.dy;
      var current = 0;

      for (var i = 0; i < pages.length; i++) {
        var raster = elements[pages[i]];
        try {
          await Future.delayed(const Duration(milliseconds: 1));
          dialog?.setProgress(current / pages.length);
          current++;
          final height = raster.height.toDouble();
          final width = raster.width.toDouble();
          final pageName = getPageName(i);
          final element = PdfElement(
            height: height,
            width: width,
            source: _pdfImportSource,
            page: i,
            position: Point(firstPos.dx, y),
            invert: invert,
            background: background,
          );
          final area = Area(
            height: height,
            width: width,
            position: Point(firstPos.dx, y),
            name: pageName,
          );
          if (spreadToPages) {
            documentPages.add((
              pageName,
              DocumentPage(
                layers: [
                  DocumentLayer(content: [element], id: createUniqueId()),
                ],
                areas: [if (createAreas) area],
              ),
            ));
          } else {
            selectedElements.add(element);
            areas.add(area);
            y += height;
          }
        } catch (e) {
          showDialog(
            context: context,
            builder: (context) =>
                UnknownImportConfirmationDialog(message: e.toString()),
          );
        }
      }
      dialog?.close();
      return ImportResult(
        service: this,
        document: document,
        elements: selectedElements,
        pages: documentPages,
        areas: spreadToPages ? [] : areas,
        choosePosition: position == null,
        assets: {_pdfImportSource: bytes},
      );
    } catch (e) {
      dialog?.close();
      showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
    } finally {
      await pdfDocument?.dispose();
    }
    return null;
  }

  Future<void> export() async {
    final state = _getState();
    if (state == null) return;
    final location = state.location;
    final fileType = location.fileType;
    final currentIndexCubit = state.currentIndexCubit;
    final viewport = currentIndexCubit.state.cameraViewport;
    switch (fileType) {
      case AssetFileType.note:
      case AssetFileType.textNote:
        exportData(context, await state.saveData());
        break;
      case AssetFileType.image:
        return showDialog<void>(
          context: context,
          builder: (context) => BlocProvider.value(
            value: bloc!,
            child: GeneralExportDialog(
              options: ImageExportOptions(
                height: viewport.height?.toDouble() ?? 1000.0,
                width: viewport.width?.toDouble() ?? 1000.0,
                scale: viewport.scale,
                x: viewport.x,
                y: viewport.y,
              ),
            ),
          ),
        );
      case AssetFileType.pdf:
        return showDialog<void>(
          context: context,
          builder: (context) => BlocProvider.value(
            value: bloc!,
            child: PdfExportDialog(
              areas: state.page.areas
                  .map((e) => AreaPreset(name: e.name, area: e))
                  .toList(),
            ),
          ),
        );
      case AssetFileType.svg:
        return showDialog<void>(
          context: context,
          builder: (context) => BlocProvider.value(
            value: bloc!,
            child: GeneralExportDialog(
              options: SvgExportOptions(
                width: (viewport.width ?? 1000) / viewport.scale,
                height: (viewport.height ?? 1000) / viewport.scale,
                x: viewport.x,
                y: viewport.y,
              ),
            ),
          ),
        );
      default:
        return;
    }
  }

  Future<bool> importArchive(
    Uint8List bytes, {
    DocumentFileSystem? fileSystem,
  }) async {
    final archive = ZipDecoder().decodeBytes(bytes);
    final data = NoteData.fromArchive(archive);
    if (data.isValid) {
      fileSystem ??= getDocumentSystem();
      final document = await (await importBfly(bytes))?.export();
      if (document != null) {
        fileSystem.createFile(document.name ?? '', document.toFile());
      }
      return document != null;
    }
    return _importArchive(archive, fileSystem: fileSystem);
  }

  Future<bool> _importArchive(
    Archive archive, {
    DocumentFileSystem? fileSystem,
  }) async {
    try {
      fileSystem ??= getDocumentSystem();
      for (final file in archive) {
        const fileExtension = '.bfly';
        if (!file.name.endsWith(fileExtension)) continue;
        final bytes = file.readBytes();
        if (bytes == null) continue;
        final document = await (await importBfly(
          bytes,
          advanced: false,
        ))?.export();
        if (document != null) {
          fileSystem.createFile(file.name, document.toFile());
        }
      }
      return true;
    } catch (e) {
      showDialog(
        context: context,
        builder: (context) =>
            UnknownImportConfirmationDialog(message: e.toString()),
      );
      return false;
    }
  }
}
